package network;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import map.Item;
import map.Level;
import map.LevelFactory;
import map.Lightmap;
import map.Tile;
import map.TileType;
import objects.ItemDropFactory;
import objects.Player;
import tools.Coordinate;
import battle.BattleFactory;
import battle.BattleResult;

public class Server {

	private int serverPort;

	private int levelCounter = 1;

	// private Level currentLevel;
	// happy fixxing sucker =)
	// Level wird nicht mehr gebroadcastet sondern jeder spieler erhält sein
	// eigenes level
	private LinkedList<Level> listLevels;

	private LinkedList<User> listUsers;

	public Server() {

		this.listUsers = new LinkedList<User>();

		initializeServer();
		listen();

	}

	private void initializeServer() {
		System.out.println("Initialize Server ...");
		System.out.println("Configuration ...");
		this.serverPort = 1234;

	}

	private void listen() {

		new Thread(new Runnable() {

			@Override
			public void run() {

				try {
					ServerSocket serverSocket = new ServerSocket(serverPort);
					while (true) {
						Socket socket = serverSocket.accept();
						ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());

						UserAction userAction = (UserAction) objectInputStream.readObject();

						if (userAction.getCommand() == Commands.LOGIN) {
							loginAction(userAction, socket);
						} else if (userAction.getCommand() == Commands.MOVE) {
							moveAction(userAction);
						} else if (userAction.getCommand() == Commands.ATTACK) {
							attackAction(userAction, socket);
						} else if (userAction.getCommand() == Commands.READY) {
							readyAction(userAction);
						} else if (userAction.getCommand() == Commands.ITEMPICKUP) {
							itemPickUpAction(userAction);
						}

						socket.close();
					}

				} catch (IOException e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}

			}
		}).start();

	}

	private Level createNewLevelAction(int levelNumber) {
		Random random = new Random();
		// return
		// LevelFactory.getInstance().generateRoomAtPos((random.nextInt(6)+2)*10,
		// (random.nextInt(6)+2)*10, levelNumber);
		// return LevelFactory.getInstance().generateLevel( levelNumber);
		Level level = null;
		if (levelNumber == 1) {
			// createTown
			level = LevelFactory.getInstance().generateTown();
		} else {
			// create Dungeonlevel
			level = LevelFactory.getInstance().generateRoomAtPos((random.nextInt(6) + 2) * 5, (random.nextInt(6) + 2) * 5, levelNumber);
		}
		return level;
	}

	private void broadcastLocations() {
		LinkedList<Player> listPlayers = new LinkedList<Player>();

		for (User user : listUsers) {
			listPlayers.add(user.getPlayer());
		}

		for (User user : listUsers) {
			System.out.println("user: " + user + " : " + user.getPlayer().getCurLevelIndex());
			ServerPackage serverPackage = new ServerPackage(listPlayers, this.listLevels.get(user.getPlayer().getCurLevelIndex()).getListEnemies(),
					this.listLevels.get(user.getPlayer().getCurLevelIndex()).getListItems(), Commands.SEND_LOCATIONS);
			broadcastData(serverPackage, user);
		}
	}

	private void broadcastMap(User user, int levelIndex) {

		boolean createNewMap = false;
		if (levelIndex >= this.listLevels.size()) {
			createNewMap = true;
		} else if (this.listLevels.get(levelIndex) == null) {
			createNewMap = true;
		}

		if (createNewMap) {
			levelCounter++;
			this.listLevels.add(createNewLevelAction(levelCounter));
			levelIndex = listLevels.size() - 1;
		}

		/*
		 * levelCounter++;
		 * this.listLevels.add(createNewLevelAction(levelCounter)); int
		 * levelIndex = listLevels.size() - 1;
		 */

		Random random = new Random();

		LinkedList<Player> listPlayers = new LinkedList<Player>();

		Coordinate coord = null;
		if (levelIndex > user.getPlayer().getCurLevelIndex()) {
			if (createNewMap) {
				List<Coordinate> listPossibleCoords = new ArrayList<Coordinate>();

				for (int x = 0; x < this.listLevels.get(levelIndex).getTiles().length; x++) {
					for (int y = 0; y < this.listLevels.get(levelIndex).getTiles()[x].length; y++) {
						if (this.listLevels.get(levelIndex).getTiles()[x][y] != null) {
							if (this.listLevels.get(levelIndex).getTiles()[x][y].isAccessible()) {
								listPossibleCoords.add(new Coordinate(x, y));
							}
						}
					}
				}
				coord = listPossibleCoords.get(random.nextInt(listPossibleCoords.size()));
				this.listLevels.get(levelIndex).getTiles()[coord.getX()][coord.getY()] = new Tile(TileType.STAIRS_UP);
			} else {
				for (int x = 0; x < this.listLevels.get(levelIndex).getTiles().length; x++) {
					for (int y = 0; y < this.listLevels.get(levelIndex).getTiles()[x].length; y++) {
						if (this.listLevels.get(levelIndex).getTiles()[x][y] != null) {
							if (this.listLevels.get(levelIndex).getTiles()[x][y].getTileType() == TileType.STAIRS_UP) {
								coord = new Coordinate(x, y);
								break;
							}
						}
					}
				}
			}
		} else {
			for (int x = 0; x < this.listLevels.get(levelIndex).getTiles().length; x++) {
				for (int y = 0; y < this.listLevels.get(levelIndex).getTiles()[x].length; y++) {
					if (this.listLevels.get(levelIndex).getTiles()[x][y] != null) {
						if (this.listLevels.get(levelIndex).getTiles()[x][y].getTileType() == TileType.STAIRS_DOWN) {
							coord = new Coordinate(x, y);
							break;
						}
					}
				}
			}
		}

		for (int i = 0; i < listUsers.size(); i++) {
			if (listUsers.get(i).getUserId() == user.getUserId()) {
				listUsers.get(i).getPlayer().setCoord(coord);
				listUsers.get(i).setReady(false);
				listUsers.get(i).getPlayer().setCurLevelIndex(levelIndex);
			}
			listPlayers.add(listUsers.get(i).getPlayer());
		}

		for (int x = 0; x < this.listLevels.get(levelIndex).getTiles().length; x++) {
			for (int y = 0; y < this.listLevels.get(levelIndex).getTiles()[x].length; y++) {
				if (this.listLevels.get(levelIndex).getTiles()[x][y] != null) {
					this.listLevels.get(levelIndex).getTiles()[x][y].setHidden(true);
				}
			}
		}

		System.out.println("calculating lightmaps ...");
		for (Lightmap lightmap : user.getListLightmap()) {
			System.out.println("player has " + user.getListLightmap().size() + " lightmaps");
			System.out.println("searching for levelIndex " + levelIndex);
			if (lightmap.getLevelIndex() == levelIndex) {
				System.out.println("levelIndex found");
				for (int x = 0; x < lightmap.getLightTiles().length; x++) {
					for (int y = 0; y < lightmap.getLightTiles()[x].length; y++) {

						if (lightmap.getLightTiles()[x][y]) {

							if (x < this.listLevels.get(levelIndex).getTiles().length && y < this.listLevels.get(levelIndex).getTiles()[0].length) {
								if (this.listLevels.get(levelIndex).getTiles()[x][y] != null) {
									this.listLevels.get(levelIndex).getTiles()[x][y].setHidden(false);
									System.out.println("matching tile ... setting Hidden to false");
								}
							}

						}

					}
				}
			}
		}

		ServerPackage serverPackage = new ServerPackage(listPlayers, this.listLevels.get(levelIndex).getListEnemies(), Commands.SEND_MAP, this.listLevels
				.get(levelIndex));
		broadcastData(serverPackage, user);

	}

	private void itemPickUpAction(UserAction userAction) {

		for (int i = 0; i < this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListItems().size(); i++) {
			if (userAction.getPlayer().getCoord().getX() == this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListItems().get(i).getCoord()
					.getX()
					&& userAction.getPlayer().getCoord().getY() == this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListItems().get(i)
							.getCoord().getY()) {
				this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListItems().remove(i);
				break;
			}
		}

		broadcastLocations();
	}

	private void readyAction(UserAction userAction) {

		User user = null;
		for (int i = 0; i < listUsers.size(); i++) {
			if (listUsers.get(i).getUserId() == userAction.getUserId()) {
				user = listUsers.get(i);
			}
		}

		broadcastMap(user, userAction.getNewLevelIndex());
	}

	private boolean everyoneReady() {
		boolean result = true;
		for (User user : listUsers) {
			if (!user.isReady()) {
				result = false;
				break;
			}
		}
		return result;
	}

	private void loginAction(UserAction userAction, Socket socket) {
		if (listLevels == null) {
			listLevels = new LinkedList<Level>();
			listLevels.add(createNewLevelAction(1));
		}

		this.listUsers.add(new User(userAction.getInetAddress(), userAction.getPort(), userAction.getUserId(), userAction.getPlayer()));

		ObjectOutputStream objectOutputStream;
		try {
			objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
			objectOutputStream.writeObject(listLevels.get(0));
			objectOutputStream.close();
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		broadcastLocations();

		printStatus();
	}

	private void moveAction(UserAction userAction) {
		User user = null;
		for (int i = 0; i < listUsers.size(); i++) {

			if (listUsers.get(i).getUserId() == userAction.getUserId()) {
				listUsers.get(i).setPlayer(userAction.getPlayer());
				listUsers.get(i).setReady(userAction.getPlayer().isReady());
				user = listUsers.get(i);
				boolean lightmapUpdate = false;
				for (int n = 0; n < listUsers.get(i).getListLightmap().size(); n++) {
					if (listUsers.get(i).getListLightmap().get(n).getLevelIndex() == userAction.getLightmap().getLevelIndex()) {
						listUsers.get(i).getListLightmap().set(n, userAction.getLightmap());
						lightmapUpdate = true;
						break;
					}
				}
				
				if (!lightmapUpdate) {

					listUsers.get(i).getListLightmap().add(userAction.getLightmap());

				}
			}

		}

		if (user.getPlayer().isReady()) {
			broadcastMap(user, userAction.getNewLevelIndex());
		} else {
			calcRoundAction(user);
			broadcastLocations();
		}
	}

	private void attackAction(UserAction userAction, Socket socket) {

		BattleResult battleResult = null;

		Random random = new Random();

		User user = null;
		for (int i = 0; i < listUsers.size(); i++) {
			if (listUsers.get(i).getUserId() == userAction.getUserId()) {
				user = listUsers.get(i);
			}
		}

		for (int i = 0; i < this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().size(); i++) {
			System.out.println(userAction.getPlayer().getCoord().getX() + "/" + userAction.getPlayer().getCoord().getY() + "  "
					+ this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getxPosition() + " "
					+ this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getyPosition());
			if (this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getxPosition() == userAction.getPlayer().getCoord()
					.getX()
					&& this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getyPosition() == userAction.getPlayer()
							.getCoord().getY()) {
				System.out.println("Attack!");
				// BattleEngineCalculations ...
				System.out.println("Ich habe " + this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getCurHp() + " HP von "
						+ this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getMaxHp());
				battleResult = BattleFactory.getInstance().calcBattle(userAction.getPlayer(),
						this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i));
				if (battleResult.getDefHp() <= 0) {
					// drop loot?
					lootDropAction(user.getPlayer().getCurLevelIndex(), ItemDropFactory.getInstance().dropNewItem(
							this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getEnemyType(),
							new Coordinate(this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getxPosition(),
									this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).getyPosition())));

					this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().remove(i);

				} else {
					System.out.println("now only " + battleResult.getDefHp());
					this.listLevels.get(userAction.getPlayer().getCurLevelIndex()).getListEnemies().get(i).setCurHp(battleResult.getDefHp());
				}
				calcRoundAction(user);
				broadcastLocations();
			}
		}

		try {
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
			objectOutputStream.writeObject(battleResult);
			objectOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private void lootDropAction(int levelIndex, Item item) {
		if (item != null) {
			this.listLevels.get(levelIndex).getListItems().add(item);
		}
	}

	private void broadcastData(final ServerPackage serverPackage, final User user) {
		new Thread(new Runnable() {

			@Override
			public void run() {

				try {
					Socket socket = new Socket(user.getInetAddress(), user.getPort());
					ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
					objectOutputStream.writeObject(serverPackage);
					objectOutputStream.close();
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();

				}

			}
		}).start();
	}

	private void calcRoundAction(User user) {

		listUsers = this.listLevels.get(user.getPlayer().getCurLevelIndex()).calcRound(listUsers);

	}

	public static void main(String[] args) {
		new Server();
	}

	private void printStatus() {
		System.out.println("Hello, I'm Server " + this.toString() + " running at port " + serverPort);
		/*
		 * if (currentLevel != null) {
		 * System.out.println("This is my current Room " +
		 * currentLevel.toString()); System.out.println("There are " +
		 * listUsers.size() + " Users logged in."); for (User user : listUsers)
		 * { System.out.println(user.getInetAddress() + " " + user.getPort() +
		 * " " + user.getUserId() + " " + user.getPlayer().getCoord()); } }
		 */
	}

}
